package org.znxs.znxsframe.controller;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.dashscope.aigc.generation.GenerationResult;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.znxs.znxsframe.annotation.AuthCheck;
import org.znxs.znxsframe.bizmq.BIMessageProducer;
import org.znxs.znxsframe.common.BaseResponse;
import org.znxs.znxsframe.common.ErrorCode;
import org.znxs.znxsframe.common.ResultUtils;
import org.znxs.znxsframe.constant.BIConstant;
import org.znxs.znxsframe.constant.UserConstant;
import org.znxs.znxsframe.exception.BusinessException;
import org.znxs.znxsframe.exception.ThrowUtils;
import org.znxs.znxsframe.manager.AIManager;
import org.znxs.znxsframe.manager.RedisLimiterManager;
import org.znxs.znxsframe.model.dto.chart.*;
import org.znxs.znxsframe.model.entity.Chart;
import org.znxs.znxsframe.model.entity.User;
import org.znxs.znxsframe.model.vo.BIResponse;
import org.znxs.znxsframe.services.ChartService;
import org.znxs.znxsframe.services.UserService;
import org.znxs.znxsframe.utils.BIStrUtils;
import org.znxs.znxsframe.utils.ExcelUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 帖子接口
 */
@RestController
@RequestMapping("/chart")
@Slf4j
public class ChartController {

    @Resource
    private ChartService chartService;

    @Resource
    private UserService userService;

    @Resource
    private AIManager aiManager;

    /**
     * 限流
     */
    @Resource
    private RedisLimiterManager redisLimiterManager;

    /**
     * 自定义线程池
     */
    @Resource
    private ThreadPoolExecutor threadPoolExecutor;

    /**
     * mq 消息队列提供者
     */
    @Resource
    private BIMessageProducer biMessageProducer;

    /**
     * 1 MB
     */
    private static final long ONE_MB = 1024 * 1024L;

    /**
     * 后缀名数组
     */
    public static final List<String> suffixList = Arrays.asList("csv", "xls", "xlsx", "xlsm", "xlsb");


    // region 增删改查

    /**
     * 创建
     *
     * @param chartAddRequest
     * @param request
     * @return
     */
    @PostMapping("/add")
    public BaseResponse<Long> addChart(@RequestBody ChartAddRequest chartAddRequest, HttpServletRequest request) {
        if (chartAddRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Chart chart = new Chart();
        BeanUtils.copyProperties(chartAddRequest, chart);
        User loginUser = userService.getLoginUser(request);
        chart.setUserId(loginUser.getId());
        boolean result = chartService.save(chart);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        long newChartId = chart.getId();
        return ResultUtils.success(newChartId);
    }

    /**
     * 删除
     *
     * @param id
     * @param request
     * @return
     */
    @PostMapping("/delete")
    public BaseResponse<Boolean> deleteChart(long id, HttpServletRequest request) {
        User user = userService.getLoginUser(request);
        // 判断是否存在
        Chart oldChart = chartService.getById(id);
        ThrowUtils.throwIf(oldChart == null, ErrorCode.NOT_FOUND_ERROR);
        // 仅本人或管理员可删除
        if (!oldChart.getUserId().equals(user.getId()) && !userService.isAdmin(request)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }
        boolean b = chartService.removeById(id);
        return ResultUtils.success(b);
    }

    /**
     * 更新（仅管理员）
     *
     * @param chartUpdateRequest
     * @return
     */
    @PostMapping("/update")
    @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
    public BaseResponse<Boolean> updateChart(@RequestBody ChartUpdateRequest chartUpdateRequest) {
        if (chartUpdateRequest == null || chartUpdateRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Chart chart = new Chart();
        BeanUtils.copyProperties(chartUpdateRequest, chart);
        long id = chartUpdateRequest.getId();
        // 判断是否存在
        Chart oldChart = chartService.getById(id);
        ThrowUtils.throwIf(oldChart == null, ErrorCode.NOT_FOUND_ERROR);
        boolean result = chartService.updateById(chart);
        return ResultUtils.success(result);
    }

    /**
     * 根据 id 获取
     *
     * @param id
     * @return
     */
    @GetMapping("/get")
    public BaseResponse<Chart> getChartById(long id, HttpServletRequest request) {
        System.out.println(id);
        if (id <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Chart chart = chartService.getById(id);
        if (chart == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
        }
        return ResultUtils.success(chart);
    }

    /**
     * 分页获取列表（封装类）
     *
     * @param chartQueryRequest
     * @param request
     * @return
     */
    @PostMapping("/list/page")
    public BaseResponse<Page<Chart>> listChartByPage(@RequestBody ChartQueryRequest chartQueryRequest, HttpServletRequest request) {
        long current = chartQueryRequest.getCurrent();
        long size = chartQueryRequest.getPageSize();
        // 限制爬虫
        ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
        Page<Chart> chartPage = chartService.page(new Page<>(current, size), chartService.getQueryWrapper(chartQueryRequest));
        return ResultUtils.success(chartPage);
    }

    /**
     * 分页获取当前用户创建的资源列表
     *
     * @param chartQueryRequest
     * @param request
     * @return
     */
    @PostMapping("/my/list/page")
    public BaseResponse<Page<Chart>> listMyChartByPage(@RequestBody ChartQueryRequest chartQueryRequest, HttpServletRequest request) {
        if (chartQueryRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User loginUser = userService.getLoginUser(request);
        chartQueryRequest.setUserId(loginUser.getId());
        long current = chartQueryRequest.getCurrent();
        long size = chartQueryRequest.getPageSize();
        // 限制爬虫
        ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
        Page<Chart> chartPage = chartService.page(new Page<>(current, size), chartService.getQueryWrapper(chartQueryRequest));
        return ResultUtils.success(chartPage);
    }

    // endregion


    /**
     * 编辑（用户）
     *
     * @param chartEditRequest
     * @param request
     * @return
     */
    @PostMapping("/edit")
    public BaseResponse<Boolean> editChart(@RequestBody ChartEditRequest chartEditRequest, HttpServletRequest request) {
        if (chartEditRequest == null || chartEditRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Chart chart = new Chart();
        BeanUtils.copyProperties(chartEditRequest, chart);
        User loginUser = userService.getLoginUser(request);
        long id = chartEditRequest.getId();
        // 判断是否存在
        Chart oldChart = chartService.getById(id);
        ThrowUtils.throwIf(oldChart == null, ErrorCode.NOT_FOUND_ERROR);
        // 仅本人或管理员可编辑
        if (!oldChart.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }
        boolean result = chartService.updateById(chart);
        return ResultUtils.success(result);
    }


    /**
     * 获取ai分析生成图表信息
     *
     * @param multipartFile       数据文件
     * @param genChartByAIRequest 生成图表请求
     * @param request
     * @return
     */
    @PostMapping("/gen")
    public BaseResponse<BIResponse> genChartByAI(@RequestPart("files") MultipartFile multipartFile, GenChartByAIRequest genChartByAIRequest, HttpServletRequest request) {

        System.out.println(multipartFile);

        // 校验 参数
        String name = genChartByAIRequest.getName();
        String goal = genChartByAIRequest.getGoal();
        String chartType = genChartByAIRequest.getChartType();
        ThrowUtils.throwIf(StrUtil.isBlank(goal), ErrorCode.PARAMS_ERROR, "目标不能为空");
        ThrowUtils.throwIf(StrUtil.isBlank(chartType), ErrorCode.PARAMS_ERROR, "图表类型不能为空");
        ThrowUtils.throwIf(StrUtil.isNotBlank(name) && name.length() > 100, ErrorCode.PARAMS_ERROR, "名称过长");
        // 校验 用户传入文件
        // 文件大小限制
        long size = multipartFile.getSize();
        ThrowUtils.throwIf(size > ONE_MB, ErrorCode.PARAMS_ERROR, "文件大小异常");
        // 限制文件后缀名
        String suffix = FileUtil.getSuffix(multipartFile.getOriginalFilename());
        ThrowUtils.throwIf(!suffixList.contains(suffix), ErrorCode.PARAMS_ERROR, "文件后缀名错误");

        // 获取当前登录用户
        User currentUser = userService.getLoginUser(request);

        // 限流操作
        redisLimiterManager.doRateLimit("znxs-bi" + currentUser.getId());

        // csv 压缩token 使ai看得懂并简化格式
        List<Map<Integer, String>> csvData = ExcelUtils.getCsvData(multipartFile);
        ThrowUtils.throwIf(CollectionUtil.isEmpty(csvData), ErrorCode.SYSTEM_ERROR, "文件提取错误，请检查文件内容格式");
        String chartData = ExcelUtils.csvCompress(csvData);
        // 封装用户输入内容
        StringBuilder userInput = new StringBuilder();
        userInput.append("分析目标:").append(goal).append("\n");
        userInput.append("图标类型：").append(chartType).append("\n");
        userInput.append("数据:").append(chartData).append("\n");

        // 调用 ai模型管理器 进行提问
        String aiResult;
        try {
            GenerationResult generationResult = aiManager.callWithMessage(BIConstant.PRESET, userInput.toString());
            aiResult = generationResult.getOutput().getChoices().get(0).getMessage().getContent();
            System.out.println(aiResult);
        } catch (Exception e) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "ai 生成失败");
        }

        // 提取ai回答的内容信息
        // 提取图表代码
        System.out.println(aiResult);
        System.out.println("===========================================================");
        System.out.println();
        String genChart;
        {
            // 正则表达式，匹配《》包裹的内容
            String regex = "《(.*?)》";
            genChart = BIStrUtils.subStrByRegex(regex, aiResult, 5);
        }
        // 提取图表分析结果
        String genResult;
        {
            // 正则表达式，匹配<charts>包裹的内容
            String regex = "【(.*?)】";
            genResult = BIStrUtils.subStrByRegex(regex, aiResult, 0);
        }

        // 封装对象
        Chart chart = new Chart();
        chart.setName(name);
        chart.setGoal(goal);
        chart.setUserId(currentUser.getId());
        chart.setChartType(chartType);
        // 这里不封装数据  数据统一存储在新建的分表中
        // chart.setChartData(chartData);
        chart.setGenChart("{" + genChart);
        chart.setGenResult(genResult);
        chart.setStatus("wait");
        // 保存到数据库
        boolean save = chartService.save(chart);
        ThrowUtils.throwIf(!save, ErrorCode.OPERATION_ERROR, "图表保存失败");
        // 修改状态数据
        Chart updateChart = new Chart();
        updateChart.setId(chart.getId());
        updateChart.setStatus("succeed");
        chartService.updateById(updateChart);

        // 封装到返回对象中
        BIResponse biResponse = new BIResponse();
        BeanUtil.copyProperties(chart, biResponse);

        // 分表操作，把数据都传入新建的表中
        String newTableName = String.valueOf(currentUser.getId()) + '_' + chart.getId();
        // 新建表
        chartService.createUserChartTable(newTableName, ExcelUtils.getHead(csvData));
        // 插入数据
        chartService.insertUserChartTable(newTableName, ExcelUtils.getRows(csvData));

        return ResultUtils.success(biResponse);
    }

    /**
     * 获取ai分析生成图表信息（异步）
     *
     * @param multipartFile       数据文件
     * @param genChartByAIRequest 生成图表请求
     * @param request
     * @return
     */
    @PostMapping("/gen/async")
    public BaseResponse<BIResponse> genChartByAIAsync(@RequestPart("files") MultipartFile multipartFile, GenChartByAIRequest genChartByAIRequest, HttpServletRequest request) {

        // 校验 参数
        String name = genChartByAIRequest.getName();
        String goal = genChartByAIRequest.getGoal();
        String chartType = genChartByAIRequest.getChartType();
        ThrowUtils.throwIf(StrUtil.isBlank(goal), ErrorCode.PARAMS_ERROR, "目标不能为空");
        ThrowUtils.throwIf(StrUtil.isBlank(chartType), ErrorCode.PARAMS_ERROR, "图表类型不能为空");
        ThrowUtils.throwIf(StrUtil.isNotBlank(name) && name.length() > 100, ErrorCode.PARAMS_ERROR, "名称过长");
        // 校验 用户传入文件
        // 文件大小限制
        long size = multipartFile.getSize();
        ThrowUtils.throwIf(size > ONE_MB, ErrorCode.PARAMS_ERROR, "文件大小异常");
        // 限制文件后缀名
        String suffix = FileUtil.getSuffix(multipartFile.getOriginalFilename());
        ThrowUtils.throwIf(!suffixList.contains(suffix), ErrorCode.PARAMS_ERROR, "文件后缀名错误");

        // 获取当前登录用户
        User currentUser = userService.getLoginUser(request);

        // 限流操作
        redisLimiterManager.doRateLimit("znxs-bi" + currentUser.getId());

        // csv 压缩token 使ai看得懂并简化格式
        List<Map<Integer, String>> csvData = ExcelUtils.getCsvData(multipartFile);
        ThrowUtils.throwIf(CollectionUtil.isEmpty(csvData), ErrorCode.SYSTEM_ERROR, "文件提取错误，请检查文件内容格式");
        String chartData = ExcelUtils.csvCompress(csvData);
        // 封装用户输入内容
        StringBuilder userInput = new StringBuilder();
        userInput.append("分析目标:").append(goal).append("\n");
        userInput.append("图标类型：").append(chartType).append("\n");
        userInput.append("数据:").append(chartData).append("\n");

        // 封装对象
        Chart chart = new Chart();
        chart.setName(name);
        chart.setGoal(goal);
        chart.setUserId(currentUser.getId());
        chart.setChartType(chartType);
        // 这里不封装数据  数据统一存储在新建的分表中
        // chart.setChartData(chartData);
        chart.setStatus("wait");

        boolean save = chartService.save(chart);
        ThrowUtils.throwIf(!save, ErrorCode.OPERATION_ERROR, "图表保存失败");

        // 执行任务队列
        try {
            CompletableFuture.runAsync(() -> {
                // 修改图表状态为"执行中"。等执行成功后，修改为"已完成"  保存执行结果；执行失败后，状态修改为："失败"，记录任务失败信息
                // 修改状态为执行中
                boolean result = chartService.handleUpdateChartStatus(chart.getId(), "running");
                if (!result) {
                    chartService.handleUpdateChartStatusError(chart.getId(), "更新图表状态失败");
                    return;
                }

                // 调用 ai模型管理器 进行提问
                String aiResult = "";
                try {
                    GenerationResult generationResult = aiManager.callWithMessage(BIConstant.PRESET, userInput.toString());
                    aiResult = generationResult.getOutput().getChoices().get(0).getMessage().getContent();
                } catch (Exception e) {
                    chartService.handleUpdateChartStatusError(chart.getId(), "ai 生成错误");
                    return;
                }

                // 提取ai回答的内容信息
                // 提取图表代码
                String genChart;
                {
                    // 正则表达式，匹配```包裹的内容
                    String regex = "```(.*?)```";
                    genChart = BIStrUtils.subStrByRegex(regex, aiResult, 5);
                }
                // 提取图表分析结果
                String genResult;
                {
                    // 正则表达式，匹配【】包裹的内容
                    String regex = "【(.*?)】";
                    genResult = BIStrUtils.subStrByRegex(regex, aiResult, 0);
                }

                // ai 生成之后 保存数据
                Chart finishGenChart = new Chart();
                finishGenChart.setId(chart.getId());
                finishGenChart.setGenChart(genChart);
                finishGenChart.setGenResult(genResult);
                chartService.updateById(finishGenChart);

                // 修改状态为已完成
                boolean result2 = chartService.handleUpdateChartStatus(chart.getId(), "succeed");
                if (!result2) {
                    chartService.handleUpdateChartStatusError(chart.getId(), "更新图表状态失败");
                }

            }, threadPoolExecutor);
        } catch (Exception e) {
            chartService.handleUpdateChartStatusError(chart.getId(), "同时调用人数太多，请稍后再试试");
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "同时调用人数太多，请稍后再试试");
        }

        // 封装到返回对象中
        BIResponse biResponse = new BIResponse();
        BeanUtil.copyProperties(chart, biResponse);

        // 分表操作，把数据都传入新建的表中
        String newTableName = String.valueOf(currentUser.getId()) + '_' + chart.getId();
        // 新建表
        chartService.createUserChartTable(newTableName, ExcelUtils.getHead(csvData));
        // 插入数据
        chartService.insertUserChartTable(newTableName, ExcelUtils.getRows(csvData));

        return ResultUtils.success(biResponse);

    }

    /**
     * 获取ai分析生成图表信息（异步消息队列）
     *
     * @param multipartFile       数据文件
     * @param genChartByAIRequest 生成图表请求
     * @param request
     * @return
     */
    @PostMapping("/gen/async/mq")
    public BaseResponse<BIResponse> genChartByAIAsyncMQ(@RequestPart("files") MultipartFile multipartFile, GenChartByAIRequest genChartByAIRequest, HttpServletRequest request) {

        // 校验 参数
        String name = genChartByAIRequest.getName();
        String goal = genChartByAIRequest.getGoal();
        String chartType = genChartByAIRequest.getChartType();
        ThrowUtils.throwIf(StrUtil.isBlank(goal), ErrorCode.PARAMS_ERROR, "目标不能为空");
        ThrowUtils.throwIf(StrUtil.isBlank(chartType), ErrorCode.PARAMS_ERROR, "图表类型不能为空");
        ThrowUtils.throwIf(StrUtil.isNotBlank(name) && name.length() > 100, ErrorCode.PARAMS_ERROR, "名称过长");
        // 校验 用户传入文件
        // 文件大小限制
        long size = multipartFile.getSize();
        ThrowUtils.throwIf(size > ONE_MB, ErrorCode.PARAMS_ERROR, "文件大小异常");
        // 限制文件后缀名
        String suffix = FileUtil.getSuffix(multipartFile.getOriginalFilename());
        ThrowUtils.throwIf(!suffixList.contains(suffix), ErrorCode.PARAMS_ERROR, "文件后缀名错误");

        // 获取当前登录用户
        User currentUser = userService.getLoginUser(request);

        // 限流操作
        redisLimiterManager.doRateLimit("znxs-bi" + currentUser.getId());

        // csv 压缩token 使ai看得懂并简化格式
        List<Map<Integer, String>> csvData = ExcelUtils.getCsvData(multipartFile);
        ThrowUtils.throwIf(CollectionUtil.isEmpty(csvData), ErrorCode.SYSTEM_ERROR, "文件提取错误，请检查文件内容格式");
        String chartData = ExcelUtils.csvCompress(csvData);

        // 封装对象
        Chart chart = new Chart();
        chart.setName(name);
        chart.setGoal(goal);
        chart.setUserId(currentUser.getId());
        chart.setChartType(chartType);

        // 这里先封装数据，等存入另一张表之后，再删除
        chart.setChartData(chartData);
        chart.setStatus("wait");

        boolean save = chartService.save(chart);
        ThrowUtils.throwIf(!save, ErrorCode.OPERATION_ERROR, "图表保存失败");
        log.info("新建图表，【id】：" + chart.getId());

        // 执行任务队列
        try {
            // 这里不执行任务队列，采用消息队列，发送消息 发送 图表id
            biMessageProducer.sendMessage(String.valueOf(chart.getId()));
        } catch (Exception e) {
            chartService.handleUpdateChartStatusError(chart.getId(), "同时调用人数太多，请稍后再试试");
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "同时调用人数太多，请稍后再试试");
        }

        // 封装到返回对象中
        BIResponse biResponse = new BIResponse();
        BeanUtil.copyProperties(chart, biResponse);

        // 分表操作，把数据都传入新建的表中
        String newTableName = String.valueOf(currentUser.getId()) + '_' + chart.getId();
        try {
            // 新建表
            chartService.createUserChartTable(newTableName, ExcelUtils.getHead(csvData));
            // 插入数据
            chartService.insertUserChartTable(newTableName, ExcelUtils.getRows(csvData));
            // 因为上面插入了表数据，所以需要把chart表的数据删除
            Chart delDataChart = new Chart();
            delDataChart.setId(chart.getId());
            delDataChart.setChartData("");
            chartService.updateById(delDataChart);
        } catch (Exception e) {
            chartService.handleUpdateChartStatus(chart.getId(), "failed");
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "新建图表失败" + e.getMessage());
        }

        return ResultUtils.success(biResponse);
    }


    @PostMapping("/test")
    public BaseResponse<BIResponse> test(HttpServletRequest request) {
        File file = new File("src/main/resources/files/数据文件.xlsx");
        // 测试生成
        MockMultipartFile multipartFile = null;
        try {
            multipartFile = new MockMultipartFile("file", file.getName(), null, new FileInputStream(file));
        } catch (IOException e) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "生成multipartFile 失败" + e);
        }
        // 生成自动参数
        GenChartByAIRequest genChartByAIRequest = new GenChartByAIRequest();
        genChartByAIRequest.setName("火影日活");
        genChartByAIRequest.setGoal("分析火影日活情况");
        genChartByAIRequest.setChartType("折线图");

        // 调用方法
        return genChartByAIAsyncMQ(multipartFile, genChartByAIRequest, request);
    }

}
